#include "delivery_mimer.h"
#include "singleton.h"
#include <vmime/vmime.hpp>
#include <vmime/platforms/posix/posixHandler.hpp>
#include <vector>
#include <pcrecpp.h>
#include <stdlib.h>

using namespace edm::network;

namespace edm{
namespace prepare{
 
delivery_mimer_t::delivery_mimer_t(const std::string& mc_host, const std::string& mc_port):
        m_memcache_connection(mc_host,mc_port),
        m_started(false),m_callback_cooked(0),m_callback_template_missed(0),m_callback_error(0),
        m_io_work(new boost::asio::io_service::work(m_io_service)),m_thread(0)
{
    logtrace((PREPARE, "delivery_mimer_t::delivery_mimer_t memcache <%s:%s>", 
              mc_host.c_str(), mc_port.c_str()));
}

delivery_mimer_t::~delivery_mimer_t()
{
    logtrace((PREPARE, "delivery_mimer_t::~delivery_mimer_t memcache begin"));
    if(m_started) stop();
    logtrace((PREPARE, "delivery_mimer_t::~delivery_mimer_t memcache end ok."));
}

//! start mimer service
int delivery_mimer_t::start(const char* bind_ip,
                            callback_cooked_t cooked_callback , 
                            callback_template_missed_t template_missed_callback,
                            callback_error_t callback_error)
{
    logtrace((PREPARE, "delivery_mimer_t::start begin ..."));
    if(m_started)
    {
        logwarn((PREPARE, "delivery_mimer_t::start name already started."));
        return 0;
    }
    m_bind_ip = bind_ip;
    m_callback_cooked = cooked_callback;
    m_callback_template_missed = template_missed_callback;
    m_callback_error = callback_error;
    m_thread.reset(new boost::thread(
    boost::bind(&boost::asio::io_service::run, &m_io_service)));
    

    m_started = true;
    logtrace((PREPARE, "delivery_mimer_t::start end ok."));
    return 0;
}

//! stop mimer service
int delivery_mimer_t::stop()
{
    logtrace((PREPARE, "delivery_mimer_t::stop begin ..."));
    if(!m_started)
    {
        logwarn((PREPARE, "delivery_mimer_t::stop already stopped"));
        return 0;
    }    
    
    m_io_work.reset();
    m_thread->join();
    m_started = false;    
    logtrace((PREPARE, "delivery_mimer_t::stop end ok."));
    return 0;
}

//! cook delivery request
void delivery_mimer_t::cook_delivery(delivery_t* delivery)
{
    logtrace((PREPARE, "delivery_mimer_t::cook_delivery"));
    m_io_service.post(boost::bind(&delivery_mimer_t::cook_delivery_i, this, delivery));
}

std::string delivery_mimer_t::memcache_key(edm_task_id_t taskid, const char* suffix)
{
    char buffer[256] = {0};
    sprintf(buffer, "%lld%s", taskid, suffix);
    std::string result(buffer);
    return result;
}

void delivery_mimer_t::cook_delivery_i(delivery_t* delivery)
{
    logtrace((PREPARE, "delivery_mimer_t::cook_delivery_i begin ..."));
    if(delivery->state == delivery_t::UNCOOK || delivery->state == delivery_t::UNCOOK_TEMPERR)
    {
        std::string raw_subject;
        std::string raw_content;
        std::map<edm_task_id_t, std::string>::iterator it = m_templates.find(delivery->task_id);
        if(it == m_templates.end())
        {
            //! memcache connect
            if(!m_memcache_connection.connected())
            {
                int count = 0;
                while(count < MEMCACHE_CONNECT_RETRY)
                {
                    if(m_memcache_connection.connect())
                    {
                        count++;
                        continue;
                    }
                    break;
                }
                
                if(!m_memcache_connection.connected())
                {
                    logerror((PREPARE, "delivery_mimer_t::cook_delivery_i memcache_connection connected failed."));
                    if(m_callback_error) m_callback_error(delivery);
                    return;
                }
            }
            
            //! memcache get template
            std::vector<char> subject;
            std::vector<char> content;
            std::string key_sub = memcache_key(delivery->task_id, MEMCACHE_SUBJECT_SUFFIX);
            logdebug((PREPARE, "memcache_key <%s>", key_sub.c_str()));
            if(m_memcache_connection.get(key_sub, subject))
            {
                m_memcache_connection.close();
                delivery->state = delivery_t::UNCOOK_TEMPERR;
                if(m_callback_error) m_callback_error(delivery);
                if(m_callback_template_missed) m_callback_template_missed(delivery->task_id);
                logerror((PREPARE, "delivery_mimer_t::cook_delivery_i memcache_connection get subject failed."));
                return;               
            }
            raw_subject.assign(subject.begin(), subject.end());
            logdebug((PREPARE, "get from subject memcache:<%s:%s>", key_sub.c_str(), raw_subject.c_str()));
            m_subjects.insert(std::pair<edm_task_id_t, std::string>(delivery->task_id, raw_subject));
            
            std::string key_temp = memcache_key(delivery->task_id, MEMCACHE_TEMPLATE_SUFFIX);
            logdebug((PREPARE, "memcache_key <%s>", key_sub.c_str()));
            if(m_memcache_connection.get(key_temp, content))
            {
                m_memcache_connection.close();
                m_subjects.erase(delivery->task_id);
                delivery->state = delivery_t::UNCOOK_TEMPERR;
                if(m_callback_error) m_callback_error(delivery);
                if(m_callback_template_missed) m_callback_template_missed(delivery->task_id);
                logerror((PREPARE, "delivery_mimer_t::cook_delivery_i memcache_connection get content failed."));
                return;
            }
            raw_content.assign(content.begin(), content.end());
            logdebug((PREPARE, "get from content memcache:<%s:%s>", key_temp.c_str(), raw_content.c_str()));
            m_templates.insert(std::pair<edm_task_id_t, std::string>(delivery->task_id, raw_content));
        }
        else
        {
            raw_subject = m_subjects[delivery->task_id];
            raw_content = m_templates[delivery->task_id];
        }
        
        delivery->subject = raw_subject;
        //! cooking message
        if(cooking_message_i(delivery, raw_content))
        {
            delivery->state = delivery_t::UNCOOK_TEMPERR;
            if(m_callback_error) m_callback_error(delivery);
            logerror((PREPARE, "delivery_mimer_t::cook_delivery_i memcache_connection cooking_message_i failed."));
            return;
        }
        delivery->state = delivery_t::COOKED;
    }
    
    if(m_callback_cooked)
    {
        m_callback_cooked(delivery);
    }
    
    logtrace((PREPARE, "delivery_mimer_t::cook_delivery_i end ok."));
}

int delivery_mimer_t::cooking_message_i(delivery_t* delivery, std::string& temp)
{
    logtrace((PREPARE, "delivery_mimer_t::cooking_message_i begin ..."));
    // VMime initialization
    vmime::platform::setHandler<vmime::platforms::posix::posixHandler>();

    try
    {
        vmime::messageBuilder mb;

        vmime::text name(delivery->from_name, vmime::charset("utf-8"));
        // Fill in the basic fields
        mb.setExpeditor(vmime::mailbox(name, delivery->from_addr));

        vmime::addressList to;
        to.appendAddress(vmime::create <vmime::mailbox>(delivery->recipient));

        mb.setRecipients(to);
        
        std::string subject = delivery->subject;
        
        if(delivery->recipient.find("qq.com") == string::npos)
           subject.append(SUBJECT_SUFFIX);
        
        int space = rand()%7;
        subject.append(space, ' ');
        
        mb.setSubject(vmime::text(subject, vmime::charset("utf-8")));

        // Set the content-type to "text/html"
        mb.constructTextPart(vmime::mediaType
                (vmime::mediaTypes::TEXT, vmime::mediaTypes::TEXT_HTML));

        // Fill in the text part: the message is available in two formats: HTML and plain text.
        // HTML text part also includes an inline image (embedded into the message).
        vmime::htmlTextPart& textPart = *mb.getTextPart().dynamicCast <vmime::htmlTextPart>();

        //!user variable replace
        logdebug((PREPARE, "delivery_mimer_t::cooking_message_i unreplace: <%s>", temp.c_str()));
        std::string replace_message = delivery->content_top + temp + delivery->content_bottom;
        logdebug((PREPARE, "delivery_mimer_t::cooking_message_i whole tmplate: <%s>", replace_message.c_str()));
        std::vector<std::string>::iterator it;
        int i = 0;
        for(it = delivery->replace.begin();
            it != delivery->replace.end();
            it++)
        {
            char param[16] = {0};
            sprintf(param, "%s%d%s", TEMPLATE_PREFIX, i++, TEMPLATE_SUFFIX);
            pcrecpp::RE(param).GlobalReplace(*it, &replace_message);
        }
        //! sys variables replace
        pcrecpp::RE("\\$\\{to\\}").GlobalReplace(delivery->recipient, &replace_message);
        pcrecpp::RE("\\$\\{from\\}").GlobalReplace(delivery->from_addr, &replace_message);
        pcrecpp::RE("\\$\\{subject\\}").GlobalReplace(delivery->subject, &replace_message);
        
        logdebug((PREPARE, "delivery_mimer_t::cooking_message_i replaceed: <%s>", replace_message.c_str()));
        // -- message text
        textPart.setCharset(vmime::charset("utf-8"));
        textPart.setText(vmime::create <vmime::stringContentHandler>
                         (replace_message));

        // Construction
        vmime::ref <vmime::message> msg = mb.construct();

        // TODO::: add some confiure headers
        if(true)
        {
            //! add x-header-fields
            //! vmime::ref<vmime::headerField> field_from_id = vmime::headerFieldFactory::getInstance()->create(mail_conf.x_mime_envelope_from);
            //! field_from_id->setValue(from_uid);

            //! vmime::ref<vmime::headerField> field_to_id = vmime::headerFieldFactory::getInstance()->create(mail_conf.x_mime_envelope_to);
            //! field_to_id->setValue(to_uid);

            //! msg->getHeader()->appendField(field_from_id);
            vmime::ref<vmime::headerField> replyto = vmime::headerFieldFactory::getInstance()->create("Reply-to");
            replyto->setValue("<edm@sdo-service.com>");
            msg->getHeader()->appendField(replyto);
            
            vmime::ref<vmime::headerField> messgeid = vmime::headerFieldFactory::getInstance()->create("Message-Id");
            char msgid[64] = {0};
            sprintf(msgid, "%ld@%s", (long)time(0), m_bind_ip.c_str());
            messgeid->setValue(msgid);
            
            msg->getHeader()->appendField(messgeid);
        }
        
        // Raw text generation
        delivery->cooked_message = msg->generate();
    }
    // VMime exception
    catch (vmime::exception& e)
    {
        logerror((PREPARE, "delivery_mimer_t::cooking_message_i vmime::exception:<%s>",
                e.what()));
        return -1;
    }
    // Standard exception
    catch (std::exception& e)
    {
        logerror((PREPARE, "delivery_mimer_t::cooking_message_i std::exception:<%s>",
                e.what()));
        return -1;
    }
    logtrace((PREPARE, "delivery_mimer_t::cooking_message_i end ok."));
    return 0;
}

void delivery_mimer_t::timer_template_clear_i(edm_task_id_t taskid)
{
    logtrace((PREPARE, "delivery_mimer_t::timer_template_clear_i begin ..."));
    //!memcache_connection.del()
    std::string key_sub = memcache_key(taskid, MEMCACHE_SUBJECT_SUFFIX);
    std::string key_temp = memcache_key(taskid, MEMCACHE_TEMPLATE_SUFFIX);
    //! memcache connect
    if(!m_memcache_connection.connected())
    {
        if(m_memcache_connection.connect())
        {
            logerror((PREPARE, "delivery_mimer_t::timer_template_clear_i memcache_connection connected failed."));
            return;
        }
    }
    m_memcache_connection.del(key_sub);
    m_memcache_connection.del(key_temp);
    m_subjects.erase(taskid);
    m_templates.erase(taskid);
    logtrace((PREPARE, "delivery_mimer_t::timer_template_clear_i end ok."));
}

}}

